# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""
Default Django settings for the Debusine signing project.

Most settings are documented in this file and they are initialized to some
reasonable default values when possible.  They will be extended (and
possibly overridden) by settings from the other modules in this package
depending on the setup selected by the administrator. You likely won't
have to modify that file.

You should instead create local.py to put your site-specific settings (use
local.py.sample as template).

Here are the most important settings:

:py:data:`DEBUSINE_DATA_PATH`
    The directory where debusine will hold its data. The directory is
    further sub-divided in multiple directories for specific use
    cases (e.g. cache, keyring, static, media, logs, templates, etc.).
    Defaults to the "data" sub-directory in the debusine
    base directory (where the code lives).

Some settings have default values which are computed dynamically from
other settings. Those settings can also be overridden. Here's the list
of those settings.

:py:data:`DEBUSINE_LOG_DIRECTORY`
    This directory will hold log files generated by debusine.
    Defaults to the "logs" sub-directory of py:data:`DEBUSINE_DATA_PATH`.

More settings:

"""
import os.path
from collections.abc import Callable
from os.path import dirname
from pathlib import Path
from typing import Any

import django
from django.core.exceptions import ImproperlyConfigured

from debusine.signing.signing_utils import read_private_key

if django.VERSION < (4, 2):
    raise ImproperlyConfigured("Debusine needs Django >= 4.2")

# Django's debug mode, never enable this in production
DEBUG = False

BASE_DIR = dirname(dirname(dirname(dirname(__file__))))
DEBUSINE_DATA_PATH = os.path.join(BASE_DIR, 'data')

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = 'Etc/UTC'

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

INSTALLED_APPS: list[str] = [
    'debusine.signing',
    'debusine.signing.db',
]

# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(asctime)s [%(module)s/%(process)d/%(thread)d] '
            + '%(levelname)s: %(message)s'
        },
        'standard': {
            'format': '%(asctime)s %(process)d %(levelname)s: %(message)s'
        },
        'simple': {'format': '%(asctime)s %(levelname)s: %(message)s'},
    },
    'filters': {
        'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'},
        'require_debug_true': {'()': 'django.utils.log.RequireDebugTrue'},
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
            'filters': ['require_debug_true'],
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler',
        },
        'null': {'level': 'DEBUG', 'class': 'logging.NullHandler'},
        'errors.log': {
            'level': 'WARNING',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': 'errors.log',
            'encoding': 'utf-8',
            'formatter': 'verbose',
            'when': 'W0',
            'backupCount': 52,
        },
        'debug.log': {
            'level': 'DEBUG',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': 'debug.log',
            'encoding': 'utf-8',
            'formatter': 'verbose',
            'when': 'W0',
            'backupCount': 52,
        },
    },
    'loggers': {
        'root': {
            'handlers': ['errors.log'],
            'level': 'ERROR',
            'propagate': False,
        },
        'django.request': {
            'handlers': ['errors.log', 'mail_admins'],
            'level': 'ERROR',
            'propagate': False,
        },
        'django.security': {
            'handlers': ['errors.log', 'mail_admins'],
            'level': 'ERROR',
            'propagate': False,
        },
        'django.security.DisallowedHost': {
            'handlers': ['errors.log'],
            'level': 'ERROR',
            'propagate': False,
        },
        'py.warnings': {'handlers': ['console']},
        'debusine': {
            'handlers': ['debug.log', 'errors.log', 'console'],
            'level': 'DEBUG',
            'propagate': False,
        },
    },
}

# === Debusine specific settings ===

# Encryption keys used by the signing service to encrypt private keys; the
# first in the list is used for newly-encrypted private keys, while the
# others may also be used for decryption
DEBUSINE_SIGNING_PRIVATE_KEYS = [
    read_private_key(path)
    for path in sorted(Path(__file__).parent.glob("*.key"))
]

# Allow signing objects that declare a trust chain to these certificate
# fingerprints.  See
# https://wiki.debian.org/SecureBoot/Discussion#Describing_the_trust_chain.
DEBUSINE_SIGNING_TRUSTED_CERTS: list[str] = []

# The lambda functions are evaluated at the end of the settings import
# logic. They provide default values to settings which have not yet been
# set (neither above nor in local.py).
_COMPUTE_DEFAULT_SETTINGS: tuple[
    tuple[str, Callable[[dict[str, Any]], str]], ...
] = (
    (
        'DEBUSINE_LOG_DIRECTORY',
        lambda t: os.path.join(t['DEBUSINE_DATA_PATH'], 'logs'),
    ),
)


def compute_default_settings(target: dict[str, Any]) -> None:
    """
    Dynamically generate some default settings.

    There are many settings whose default value depends on another
    setting. They are defined in the _COMPUTE_DEFAULT_SETTINGS dict
    with a function that evaluates them.
    """
    for setting, value in _COMPUTE_DEFAULT_SETTINGS:
        if setting in target:
            continue  # Settings is already defined
        target[setting] = value(target)
    # Update LOGGING with full paths
    for handler in target['LOGGING']['handlers'].values():
        if 'filename' not in handler or "/" in handler['filename']:
            continue
        handler['filename'] = os.path.join(
            target['DEBUSINE_LOG_DIRECTORY'], handler['filename']
        )
    # Update DATABASES with full paths
    dbconf = target['DATABASES']['default']
    if dbconf['ENGINE'] == 'django.db.backends.sqlite3':
        if '/' not in dbconf['NAME']:
            dbconf['NAME'] = os.path.join(
                target['DEBUSINE_DATA_PATH'], dbconf['NAME']
            )
        if (
            'TEST' in dbconf
            and 'NAME' in dbconf['TEST']
            and '/' not in dbconf['TEST']['NAME']
        ):
            dbconf['TEST']['NAME'] = os.path.join(
                target['DEBUSINE_DATA_PATH'], dbconf['TEST']['NAME']
            )
